/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.projects.csv.business.manager.impl;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.el.ValueExpression;

import org.apache.commons.beanutils.PropertyUtils;

import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.basics.exception.technical.TechnicalException;
import com.genia.toolbox.basics.manager.ExceptionManager;
import com.genia.toolbox.persistence.bean.Persistable;
import com.genia.toolbox.persistence.dao.AbstractDAO;
import com.genia.toolbox.persistence.exception.ObjectIsNotPersistableException;
import com.genia.toolbox.projects.csv.bean.Action;
import com.genia.toolbox.projects.csv.bean.ChooseAction;
import com.genia.toolbox.projects.csv.bean.DeleteAction;
import com.genia.toolbox.projects.csv.bean.ExecuteAction;
import com.genia.toolbox.projects.csv.bean.IfAction;
import com.genia.toolbox.projects.csv.bean.IterateAction;
import com.genia.toolbox.projects.csv.bean.NewAction;
import com.genia.toolbox.projects.csv.bean.SaveAction;
import com.genia.toolbox.projects.csv.bean.SetAction;
import com.genia.toolbox.projects.csv.bean.SetI18nDynamicValueAction;
import com.genia.toolbox.projects.csv.business.exception.MappingException;
import com.genia.toolbox.projects.csv.business.manager.ExecutionActionVisitor;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.I18nDynamicString;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.I18nDynamicValue;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.impl.I18nDynamicStringImpl;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.impl.I18nDynamicTextImpl;
import com.genia.toolbox.projects.toolbox_basics_project.spring.manager.I18nDynamicEntityManager;

/**
 * implementation of {@link ExecutionActionVisitor}.
 */
public class ExecutionActionVisitorImpl
    extends AbstractDAO
    implements ExecutionActionVisitor
{

  /**
   * error key when there is a problem with setI18nValue.
   */
  public static final String KEY_SET_I18N_VALUE_ERROR = ExecutionActionVisitorImpl.class.getName() + ".error.setI18nDynamicValue";

  /**
   * the {@link CsvVariableResolver} to resolve the variable names.
   */
  private final ThreadLocal<CsvVariableResolver> csvVariableResolver = new ThreadLocal<CsvVariableResolver>();

  /**
   * the {@link ExceptionManager} to use.
   */
  private ExceptionManager exceptionManager;

  /**
   * the {@link I18nDynamicEntityManager} to use.
   */
  private I18nDynamicEntityManager i18nDynamicEntityManager;



  /**
   * clear the setted values to prevent memory leak.
   */
  public void clearVisitor()
  {
    csvVariableResolver.remove();
  }



  /**
   * getter for the exceptionManager property.
   * 
   * @return the exceptionManager
   */
  public ExceptionManager getExceptionManager()
  {
    return exceptionManager;
  }



  /**
   * setter for the csvVariableResolver property.
   * 
   * @param csvVariableResolver
   *          the csvVariableResolver to set
   */
  public void setCsvVariableResolver(CsvVariableResolver csvVariableResolver)
  {
    this.csvVariableResolver.set(csvVariableResolver);
  }



  /**
   * setter for the exceptionManager property.
   * 
   * @param exceptionManager
   *          the exceptionManager to set
   */
  public void setExceptionManager(ExceptionManager exceptionManager)
  {
    this.exceptionManager = exceptionManager;
  }



  /**
   * visit method for {@link ChooseAction}.
   * 
   * @param action
   *          the action to visit
   * @throws BundledException
   *           if an error occured
   */
  public void visitChooseAction(ChooseAction action)
      throws BundledException
  {
    for (IfAction ifAction : action.getActions()) {
      if (executeIfAction(ifAction)) {
        return;
      }
    }
    for (Action otherwise : action.getOtherwise()) {
      otherwise.visit(this);
    }
  }



  /**
   * visit method for {@link DeleteAction}.
   * 
   * @param action
   *          the action to visit
   * @throws BundledException
   *           if an error occured
   */
  public void visitDeleteAction(DeleteAction action)
      throws BundledException
  {
    Object entity = csvVariableResolver.get().getEntitiesValues().get(action.getEntity());
    if (!(entity instanceof Persistable<?>)) {
      throw new ObjectIsNotPersistableException();
    }
    delete((Persistable<?>) entity);
  }



  /**
   * visit method for {@link ExecuteAction}.
   * 
   * @param action
   *          the action to visit
   * @throws BundledException
   *           if an error occured
   */
  public void visitExecuteAction(ExecuteAction action)
      throws BundledException
  {
    Object o = parseELExpression(action.getObject());
    int nbArguments = action.getArguments().size();
    for (Method method : o.getClass().getMethods()) {
      if (Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers()) && method.getParameterTypes().length == nbArguments && method.getName().equals(action.getMethod())) {
        try {
          Object[] arguments = new Object[nbArguments];
          for (int i = 0; i < arguments.length; i++) {
            arguments[i] = parseELExpression(action.getArguments().get(i), method.getParameterTypes()[i]);
          }
          method.invoke(o, arguments);
        }
        catch (Exception e) {
          throw getExceptionManager().convertException(e);
        }
      }
    }
  }



  /**
   * visit method for {@link IfAction}.
   * 
   * @param action
   *          the action to visit
   * @throws BundledException
   *           if an error occured
   */
  public void visitIfAction(IfAction action)
      throws BundledException
  {
    executeIfAction(action);
  }



  /**
   * visit method for {@link IterateAction}.
   * 
   * @param action
   *          the action to visit
   * @throws BundledException
   *           if an error occured
   */
  public void visitIterateAction(IterateAction action)
      throws BundledException
  {
    Map<String, Object> entities = csvVariableResolver.get().getEntitiesValues();
    try {
      csvVariableResolver.get().setEntitiesValues(new HashMap<String, Object>(entities));
      Object values = parseELExpression(action.getExpression());
      if (values instanceof Map) {
        values = ((Map<?, ?>) values).values();
      }
      if (!(values instanceof Collection<?>)) {
        values = Arrays.asList(values);
      }
      Collection<?> valuesCollection = (Collection<?>) values;
      for (Object value : valuesCollection) {
        csvVariableResolver.get().getEntitiesValues().put(action.getVar(), value);
        for (Action insideAction : action.getActions()) {
          insideAction.visit(this);
        }
      }
    }
    finally {
      csvVariableResolver.get().setEntitiesValues(entities);
    }
  }



  /**
   * visit method for {@link NewAction}.
   * 
   * @param action
   *          the action to visit
   * @throws BundledException
   *           if an error occured
   */
  public void visitNewAction(NewAction action)
      throws BundledException
  {
    try {
      csvVariableResolver.get().getEntitiesValues().put(action.getVar(), action.getTypeClass().newInstance());
    }
    catch (Exception e) {
      throw getExceptionManager().convertException(e);
    }
  }



  /**
   * visit method for {@link SaveAction}.
   * 
   * @param action
   *          the action to visit
   * @throws BundledException
   *           if an error occured
   */
  public void visitSaveAction(SaveAction action)
      throws BundledException
  {
    Object entity = csvVariableResolver.get().getEntitiesValues().get(action.getEntity());
    if (!(entity instanceof Persistable<?>)) {
      throw new ObjectIsNotPersistableException();
    }
    saveOrUpdate((Persistable<?>) entity);
  }



  /**
   * visit method for {@link SetAction}.
   * 
   * @param action
   *          the action to visit
   * @throws BundledException
   *           if an error occured
   */
  public void visitSetAction(SetAction action)
      throws BundledException
  {
    csvVariableResolver.get().getEntitiesValues().put(action.getVar(), parseELExpression(action.getExpression()));
  }



  /**
   * visit method for {@link SetI18nDynamicValueAction}.
   * 
   * @param action
   *          the action to visit
   * @throws BundledException
   *           if an error occured
   */
  public void visitSetI18nDynamicValueAction(SetI18nDynamicValueAction action)
      throws BundledException
  {
    Object entity = csvVariableResolver.get().getEntitiesValues().get(action.getEntity());
    @SuppressWarnings("unchecked")
    Map<Locale, String> values = (Map<Locale, String>) csvVariableResolver.get().getFieldsValues().get(action.getField());

    I18nDynamicValue i18nDynamicValue = null;
    try {
      i18nDynamicValue = (I18nDynamicValue) PropertyUtils.getProperty(entity, action.getProperty());
    }
    catch (Exception e) {
      throw new MappingException(e, KEY_SET_I18N_VALUE_ERROR, action.getFolderDescriptor().getFolderName(), action.getEntity(), action.getProperty(), action.getField());
    }
    if (i18nDynamicValue == null) {
      try {
        if (I18nDynamicString.class.isAssignableFrom(PropertyUtils.getPropertyDescriptor(entity, action.getProperty()).getPropertyType())) {
          i18nDynamicValue = new I18nDynamicStringImpl();
        }
        else {
          i18nDynamicValue = new I18nDynamicTextImpl();
        }
      }
      catch (Exception e) {
        throw new MappingException(e, KEY_SET_I18N_VALUE_ERROR, action.getFolderDescriptor().getFolderName(), action.getEntity(), action.getProperty(), action.getField());
      }
    }

    for (Entry<Locale, String> entry : values.entrySet()) {
      getI18nDynamicEntityManager().setValue(i18nDynamicValue, entry.getKey(), entry.getValue());
    }
  }



  /**
   * execute an {@link IfAction}.
   * 
   * @param action
   *          the action to execute
   * @return <code>true</code> if the test returns <code>true</code>, false
   *         otherwise
   * @throws BundledException
   *           if an error occured
   */
  private boolean executeIfAction(IfAction action)
      throws BundledException
  {
    boolean res = parseELExpression(action.getTest(), Boolean.class);
    if (res) {
      final Map<String, Object> entities = csvVariableResolver.get().getEntitiesValues();
      try {
        for (Action insideAction : action.getActions()) {
          insideAction.visit(this);
        }
      }
      finally {
        csvVariableResolver.get().setEntitiesValues(entities);
      }

    }
    return res;
  }



  /**
   * parse an EL expression and returns the result.
   * 
   * @param elExpression
   *          the expression to parse
   * @return the result of the expression
   * @throws TechnicalException
   *           if an error occured
   */
  private Object parseELExpression(String elExpression)
      throws TechnicalException
  {
    return parseELExpression(elExpression, Object.class);
  }



  /**
   * parse an EL expression and returns the result.
   * 
   * @param <TYPE>
   *          the type of the computed expression
   * @param elExpression
   *          the expression to parse
   * @param expectedType
   *          the expected type of the expression
   * @return the result of the expression
   * @throws TechnicalException
   *           if an error occured
   */
  @SuppressWarnings("unchecked")
  private <TYPE> TYPE parseELExpression(String elExpression, Class<TYPE> expectedType)
      throws TechnicalException
  {
    // The conversion to String replace null with "", to prevent that when a String is searched, the resolution is done againts Object and a String.valueOf() is then executed.
    ValueExpression createValueExpression = String.class.equals(expectedType) ? CsvVariableResolver.EXPRESSION_FACTORY.createValueExpression(csvVariableResolver.get(), elExpression, Object.class)
        : CsvVariableResolver.EXPRESSION_FACTORY.createValueExpression(csvVariableResolver.get(), elExpression, expectedType);
    Object result = createValueExpression.getValue(csvVariableResolver.get());
    if (result == null) {
      return (TYPE) result;
    }
    return (TYPE) (String.class.equals(expectedType) ? result.toString() : result);
  }



  /**
   * getter for the i18nDynamicEntityManager property.
   * 
   * @return the i18nDynamicEntityManager
   */
  public I18nDynamicEntityManager getI18nDynamicEntityManager()
  {
    return i18nDynamicEntityManager;
  }



  /**
   * setter for the i18nDynamicEntityManager property.
   * 
   * @param dynamicEntityManager
   *          the i18nDynamicEntityManager to set
   */
  public void setI18nDynamicEntityManager(I18nDynamicEntityManager dynamicEntityManager)
  {
    i18nDynamicEntityManager = dynamicEntityManager;
  }

}
